Skip to content

Knowledge graph — see, ask, and measure how spec, code, tests, and docs connect#222

Merged
qwerfunch merged 29 commits into
developfrom
feature/ssot-knowledge-graph
Jul 1, 2026
Merged

Knowledge graph — see, ask, and measure how spec, code, tests, and docs connect#222
qwerfunch merged 29 commits into
developfrom
feature/ssot-knowledge-graph

Conversation

@qwerfunch

Copy link
Copy Markdown
Owner

2026-07-01 · feature/ssot-knowledge-graph → develop

A live graph of how spec, code, tests, and docs connect — see it, ask what a
change breaks, and measure how little you actually need to read for a task.
Purely additive.

Heads-up: No existing command changes behavior. The graph is computed on
demand and never written to disk — no new files, no persisted state — so this
merge is low-risk. One new automated check (broken doc links) joins the suite:
clean on this repo, silent unless a doc link is actually broken.

Added

  • See — clad graph serve opens a live view in the browser; clad graph export writes it to a file (Mermaid, DOT, JSON, or Obsidian canvas). Colour-coded by node type (spec, code, test, doc).
  • Ask — clad impact <feature|file>: what depends on it, and which tests to re-run. clad context <feature>: the minimal working set — the short list of files and specs a task actually needs.
  • Measure — clad measure: how much less you need to read for a task versus opening the whole repo (about 2.7x less, median across this repo's features).
  • Docs — one check flags dead doc links and out-of-date feature references. Separately, clad infer-deps reads your code's import statements to suggest which features depend on which (read-only — it never edits your spec).

Changed

  • All four READMEs (EN/KO, HTML/MD) refreshed for the new capability; counts updated — 40 detectors, 199 features, 1665 tests.

Fixed

  • clad graph export cut off large piped output at 64 KiB (~64,000 bytes): the command exited before all the data reached the next command. Writing straight to a file was never affected. Now fixed, with a regression test; caught by an end-to-end run before this PR.

1665/1665 tests · full gate green.

🤖 Generated with Claude Code

qwerfunch and others added 29 commits June 29, 2026 14:34
Derive three reverse maps from the spec's forward edges, memoized per
Spec instance via a WeakMap (no Spec mutation, 0 bytes on disk):
- dependents:        featureId -> direct dependents (inverse depends_on)
- moduleOwners:      module path -> owning feature ids (many-to-many)
- testRefCitations:  test path -> citing feature ids (anchor-stripped)

The backlink seam the graph layer (blast-radius queries, doc linking,
exports) reuses. pruneToFeature only walked depends_on upward; this is
the reverse direction. Tests authored impl-blind (4/4 green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
buildImpactSlice + clad_get_impact (MCP) + clad impact (CLI): the
backward complement of clad_get_context. Resolve a feature id/slug or a
module path (fanning out through the many-to-many module owners) and walk
the reverse-index dependents to return the blast radius — impacted
features, scenarios at risk, the deduped regression test set, and the
modules in the radius. Bounded by optional depth, deterministic.

Live: `clad impact src/spec/load.ts` → 4 owners, 111 impacted features,
115 regression tests. Logic tested impl-blind (4/4); MCP integration
tested (clad_get_impact). Glossary + verb-list updated for the new surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…f643e

extractDocReferences scans docs/**/*.md (excluding fixture/benchmark
dirs, stripping code spans) for F-id references and resolved relative
.md links; clad sync materialises spec/_doc-links.yaml (Tier C, the
greppable "which docs explain feature X" index). New DOC_LINK_INTEGRITY
detector (#38): a dead relative .md link is an error; a scoped doc's
unresolved F-id is a warn. Per-file `clad-doc-links: ignore` opt-out for
teaching docs that use illustrative ids (spec-ids-multi-dev, ssot-testing).

The "all documents connected, always current" half of the graph, made
mechanical. Detector green on cladding-self (scoping = zero false-RED).
Logic + detector tested impl-blind (6/6). Detector count 37→38 across
prose claims; A/B reports regenerated for the new info finding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
src/graph/: buildGraph materialises ONE typed graph (feature/module/test/
scenario/capability/doc nodes; depends_on/touches/covers/binds/implements/
references/links edges) from spec + reverse-index + doc-links. Renderers:
mermaid + DOT + JSON (stdout) and an Obsidian vault (one note per node with
[[wikilinks]] + Backlinks) — see the graph in best-in-class viewers, no
bespoke UI. `clad graph stats` ranks hubs by degree (what's load-bearing).
`--focus <q> --depth N` exports a legible neighborhood subgraph.

Declared a `graph` foundation layer in architecture.yaml (pure spec reader).
Live: 706 nodes / 1247 edges; top hub src/cli/clad.ts (degree 33). Pure
functions tested impl-blind (6/6). Verb count 19→20; glossary + SVG diagram
counts (→38 detectors) updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ndexes

- spec/capabilities.yaml: new `knowledge-graph` capability (Tier B) grouping
  the four graph features (F-ee47fc2b, F-7794a6bc, F-ee5f643e, F-569f4b37).
- CHANGELOG [Unreleased]: knowledge-graph entry, framed as traceability/
  retrieval (NOT correctness — honest per the A/B record).
- clad sync refresh: spec/index.yaml, spec/_doc-links.yaml, attestation re-stamp.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… F-02343cd1

The graph, seen our own way (more than the spec asked):
- model: GraphNode.tier (A/B/C/D) classified deterministically — features/
  scenarios=A, capabilities=B, docs by first-line `Cladding · Tier X` banner
  (+ known-filename fallback), modules/tests=code (no tier). Feature labels now
  show the slug (slug ?? title ?? id); full title kept as `detail` for hover.
- render: getTierColor + getTierLegend; tier flows into json, obsidian
  frontmatter, and mermaid (per-tier classDef coloring — bonus).
- viewer-shell + src/graph/viewer/{app.js,styles.css}: toHtmlShell emits ONE
  self-contained, offline, double-clickable .html — a hand-rolled, zero-dep
  canvas force-directed renderer (NOT a vendored 80KB lib: truest to the
  zero-dep ethos, full control, ~no supply-chain surface). Tier colors, slug
  labels, status opacity, degree-sized hubs, a sidebar (search, kind+tier
  filters with counts, tier legend, Calm/Live, theme, labels), hover-
  neighborhood, click-to-pin, zoom/pan, localStorage layout persistence.
- `clad graph export --format html --out f.html` (mandatory --out). Build copies
  viewer assets to dist/viewer/ (schema.json precedent). eslint-ignored.

Live: 710 nodes / 1259 edges → 213KB self-contained .html, byte-deterministic,
zero external requests. Tests authored impl-blind (3/3). gate GREEN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…64a5c159

The graph as a LIVE view, not a re-exported snapshot (the user's reframe:
the graph is a pure derivation of the spec, so build the view once and let
it auto-update as development proceeds).

- src/cli/graph-serve.ts: `clad graph serve [--port]` — a stdlib node:http
  server (zero deps). GET / serves the viewer (with an injected SSE reload
  snippet), GET /graph.json recomputes buildGraph on EVERY request (always
  current — no stale-trap), GET /events is a text/event-stream channel.
  node:fs.watch on spec/ + docs/ broadcasts a debounced refresh → open
  browsers auto-reload. Hardened: headersSent-guarded error path +
  closeAllConnections so it shuts down cleanly (and survives EventSource
  disconnects). SSE keep-alive every 30s.
- src/serve/server.ts: clad_get_graph MCP tool — agents read the live
  (optionally focused) graph in one call; never stale.

Live: GET /graph.json reflects the current spec (713 nodes after this
feature landed). Endpoints + broadcast tested impl-blind (2/2); clad_get_graph
tested over MCP. glossary + TOOL_NAMES updated. gate GREEN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ELOG

- spec/capabilities.yaml: knowledge-graph now also owns the HTML viewer
  (F-02343cd1) and live serve (F-64a5c159).
- CHANGELOG [Unreleased]: the SSoT-tier-colored viewer + the live auto-updating
  serve, framed plainly (see/navigate the structure, not a correctness check).
- clad sync refresh: index.yaml, _doc-links.yaml, attestation re-stamp.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t + sidebar-aware fit

The canvas had no CSS width/height, so it fell back to the intrinsic 300×150
and sat hidden behind the 264px sidebar — the graph area rendered blank.
Size the canvas to 100vw/100vh and offset fit() by the sidebar width so the
graph centers in the visible area. Verified headless: nodes draw (ctx.arc
fires 31k× across the settle frames on the 713-node self-graph).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oom — F-8234ec3c

Make it extreme, and alive at rest (the user's ask):
- LAYOUT: a degree-weighted radial galaxy. High-degree hubs are pulled toward
  the centre, low-degree leaves toward the rim; charge spreads angularly,
  springs cluster the connected — the graph gathers into one circular galaxy
  with a bright load-bearing core. Verified on the real 717-node graph: 0
  NaN/Inf, hub (deg 35) at dist 412 vs median 987 (central).
- ALIVE AT REST: ambient is now the default — a slow global rotation + node
  breathing + flowing edge particles + hub glow pulse, all O(n)/O(edges) draw
  (rotation is a free transform; physics stays burst-only). "Calm" freezes it.
- LOOK: additive ("lighter") bloom pass so overlapping hubs build a glowing
  core, over a deep-space radial-gradient background. Click-to-focus (persistent
  neighborhood highlight + smooth recenter), smooth view lerp, upright labels.

Plus a headless render test (tests/graph/viewer-render.test.ts): stubs canvas/
document/window, runs the real app.js, and asserts it draws nodes, keeps the
ambient loop alive, and settles to finite positions with the hub central —
guarding the "blank canvas" / NaN-blowup regressions deterministically in-gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… F-04f50847, F-af45042a

Re-architect the viewer to Obsidian quality, and add the killer that only a
spec↔code-connected tool can do.

Obsidian-grade (F-04f50847):
- Continuous low-alpha simulation with an alphaTarget thermostat: dragging a node
  reheats so connected nodes follow elastically (real tension); HOVER pauses the
  sim (motion freezes under the cursor); release decays to rest. Frame-time
  normalized, slow/calm. Removed the global rotation + edge particles + the
  forced radial-by-degree layout (→ organic center+charge+link balance).
- Four force sliders (중심 장력 / 반발력 / 링크 장력 / 링크 거리) live-bound + persisted.
- nodeColor separates all classes: tiers A/B/C/D distinct hues + module(orange)/
  test(green)/doc(pink) distinct (were all gray).

KILLER — live conformance heal (F-af45042a):
- src/stages/graph-health.ts: nodeHealth() runs cladding's drift detectors and
  maps each finding to its graph node (path→module/test/doc, F-id→feature),
  worst-severity per node. (Lives in stages/: graph→stages is forbidden,
  stages→graph is fine.)
- clad graph serve: GET /health.json (live, watch-refreshed); the viewer overlays
  problem nodes (error=red pulse / warn=amber) over the pretty default + an
  in-sync% pill, and heals smoothly on SSE refresh (viewer self-wires events;
  the reload injection is gone). Static export embeds a stamped snapshot.
- Verified live: /health.json flags the exact features whose modules drifted from
  attestation; they heal green once the gate re-attests.

Galaxy viewer (F-8234ec3c) archived — superseded by F-04f50847 (radial layout,
global rotation, particles replaced; bloom + click-focus carried forward).
Headless tests rewritten for the new behavior (hover-pause, drag-reheat, color,
finite) + a health-mapper test. 1534 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…muddy bloom

The dense overlapping 'confetti' is now a breathing web: a collision pass
hard-separates overlapping nodes (0 overlapping pairs on the 717-node graph,
was a clump), stronger repel + smaller nodes spread it (span ~1350). Edges are
colored-by-source and actually visible (the structure reads); the additive
bloom (which washed the centre muddy/white) is removed; nodes get a thin
bg-colored ring so touching nodes stay crisp; health is a clean pulsing ring
not a blob. Force defaults retuned for an even, Obsidian-like distribution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…f7ead0, F-06dfdad6, F-d6b93648)

Knowledge-graph session work on feature/ssot-knowledge-graph:

- webgl-stellar-viewer (F-77f7ead0): real three.js + UnrealBloom 3D galaxy over
  the SSoT graph (semantic hue × degree luminosity) + live drift-health overlay,
  esbuild-bundled offline. Adds a 'skill' node kind (SKILL.md distinct from code).
  Archives the 2D canvas viewer (F-04f50847, superseded); swaps F-af45042a module.
- working-set-assembler (F-06dfdad6): clad_get_working_set — one token-budgeted,
  code-bearing payload (focus + module code + forward needs + backward breaks +
  verify + budget). ~5-8x smaller than reading shard+files; clad_get_context frozen.
- graph-context-wiring (F-d6b93648): PostToolUse auto impact-card after source edits
  + ai_hints/persona nudge so the working set is actually used.

All three: strict gate GREEN + attested; 1603 tests pass; tsc/eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…6250595, F-2be3e3bb, F-15999130, F-16138071)

Four features that make cladding's knowledge graph populated + measurable, on
feature/ssot-knowledge-graph. The arc: 4 A/Bs found the graph tools NULL on agent
correctness — then re-framing to the real goal (search/context efficiency, not
"smarter agents") + fixing the empty-graph root cause turned it positive.

- iterative-impact-slice (F-96250595): buildIterativeImpactSlice — seed→widen→stop
  (coverage/exhaustion/marginal-yield), self-describing depth+stoppedBy+coverage;
  fixes the fixed-depth-1 narrow-miss. Calibrated on real graph data (2 sims).
- infer-depends-on (F-2be3e3bb): clad infer-deps reconstructs feature depends_on
  from the code import graph — the load-bearing graph edge cladding PRODUCED nowhere
  (vapt shipped 0 edges → empty graph → the real cause of the A/B NULLs). vapt 0→698.
- inferable-depends-on detector (F-15999130): INFERABLE_DEPENDS_ON — single info
  finding (never blocks, even strict) surfacing the empty-graph gap so infer-deps
  isn't a latent never-run tool. Detector count 38→39.
- graph-efficiency-measure (F-16138071): clad measure — deterministic per-feature
  search/context efficiency (no agent, no NULL risk). vapt: working-set 4.1x smaller
  context than naive (shard+all modules); dependency radius + regression set resolved
  for free. The goal axis, finally quantified positive.

Honest framing throughout (docs/ab-evaluation/*.md): the graph does NOT make a capable
agent more correct/cheaper (it greps regardless — NULL x4); its value is small context
+ explicit blast radius + known regression set + a queryable dependency graph, for
humans and tools that use it. infer-deps writes nothing (reviewable suggestions only).

All gated: 1624 tests GREEN, tsc/eslint clean, strict gate + attested per feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…drop dead StopReason

Read-only adversarial audit of the session found the work correct (4 features
conform, tests honest, claims reproduce, no regressions). Two low-severity hygiene
fixes, zero functional impact:

- docs/ab-evaluation/case-efficiency-measurement.md: the table wrote "3,028 tok vs
  14,442 tok = 4.1x (median)" on one line, which reads as a contradiction (14442/3028
  ≈ 4.8). Both numbers are correct but are DIFFERENT statistics — 4.1x is the median
  of per-feature naive÷slice ratios; 3,028/14,442 are independent medians of slice
  and naive sizes. Split them + added a note so the report can't be misread. (The
  audit verdict marked 4.1x "VERIFIED" and missed this; caught by hand arithmetic.)
- src/optimizer/iterative-slice.ts: removed 'token-budget' from the StopReason union
  — it was declared but never returned (budget overflow stops at max-depth first).
  Synced the test's ALLOWED_STOPS list.

1624 tests GREEN, tsc/eslint clean, strict gate re-attested (F-96250595).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…84d0)

The final read-only graph-traversal test on the real doverunner-vapt project
measured inference precision ~100% but recall ~70-75% — the largest remaining gap
being dynamic/runtime imports (importlib.import_module, __import__, getattr-based,
require(<expr>)) that static regex extraction cannot see (e.g. catalog/tool_inventory.py).

Rather than silently under-report those edges, inferDependsOn now collects such
module files into a sorted dynamicImportFiles list so a maintainer knows exactly
which files to review by hand. This is an honest-recall surface, NOT a precision
change — the static edges from the same files are still inferred unchanged (additive).
clad infer-deps prints dynamic_import_files.

Verified: cladding-self flags scripts/build.mjs (require). blind tests 3/3 in a
separate file (existing 6 untouched); 1627 tests GREEN; tsc/eslint clean; strict
gate re-attested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s (F-5b188856)

The viewer's node colors had no clear basis: semanticHue let tier win over kind,
so one hue tried to encode two axes and collided — feature and scenario both
rendered blue, tier-C teal ≈ skill turquoise, and the sidebar showed the same
blue labeled both "Spec · sealed" (tier) and "feature" (kind). Tier is derivable
from kind, so encoding both was redundant.

Hue now encodes KIND only. semanticHue ignores tier; tier moves to the sidebar
filter + tooltip. KIND_COL becomes a grouped, simulation-verified palette
(all Y≥125 bloom floor, colorblind 4-group separation, hub-whitening distinctness):
  SPEC = blue family (feature/scenario/capability)
  module = orange (anchor) · test = green (anchor)
  DOCS = pink family (doc/skill) — skill is SKILL.md, a document, moved out of the
  old code-adjacent turquoise.

Sidebar: one color legend grouped into spec/code/test/docs zones; the SSoT tier
section is a swatch-less filter (checkbox + label only, no empty box). Tier labels
simplified to plain words (Spec / Design / Derived / Audit), and code nodes display
as "code" in the viewer (the spec's `modules:` data model is unchanged — display
label only).

Honest residuals (accepted, user-preferred anchors): module orange sits near the
0-45° health-burn arc, disambiguated by the burn's 2.2-3× pulse vs a static node;
orange↔green is the textbook red-green colorblind pair but is conceptually adjacent
(code↔test) with a luminance backstop.

1630 tests GREEN; tsc/eslint clean; strict gate re-attested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…content-accuracy pass

README.ko.html:
- enterprise trust/trace/scale hero; new project-map (graph) section with a live
  galaxy GIF, colour legend, and a clad-graph-serve launch guide
- refreshed numbers (39 detectors / 1630 tests / 196 features, 192 done / v0.7.0)
- unified plain-declarative voice; blue-primary emphasis (green = pass/success only)
- accurate 4-tier SSoT table (intent defined by humans, authored by the LLM per EARS)

docs/img/ko/*.svg (8): unified flat blue-token design system (no shadow/pastel/accent
bars, no version stamps); every claim verified against code — gate triggers 3/9/15 by
cost, authorship human-defines/LLM-writes, segregation-of-duties "aligns with" (not
"maps to") EU AI Act/SOX, Tier-B = project-context.md (not ai_hints), runner examples
= pure executors

docs/img/ko/graph.gif: 880px/8fps live-graph recording (6.9MB)
.gitignore: ignore *.mov (local screen recordings)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings develop's EARS `complex` pattern (#208) and the UNVERIFIED_AC drift
detector + multi-framework JUnit matching (#204) under the knowledge-graph branch.

Conflict resolution:
- src/stages/detectors/index.ts — union both detector imports (inferableDependsOn
  + unverifiedAc; the allDetectors array already carried both)
- detector count reconciled to 40 (37 base + develop's UNVERIFIED_AC + this branch's
  DOC_LINK_INTEGRITY & INFERABLE_DEPENDS_ON) across plugin.json (rebuilt by
  build:plugin), spec.yaml, capabilities.yaml, project-context.md, AGENTS.md,
  README.ko.html, and the ko SVGs
- CHANGELOG.md — union both [Unreleased] sections
- spec.yaml inventory re-synced via clad sync (199 features, 195 done, 6
  capabilities, 169 test files)
- README.ko.html counts refreshed (199/195 features, 1664 tests)
- dist + plugin mirrors rebuilt

Verified: npm test GREEN (1664/1664), self-consistency detector-count check passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hashes

The merge moved 66 done features' module trees. A GREEN strict pre-push gate
(type/lint/unit/coverage/deliverable-smoke all pass; STALE_ATTESTATION was the
only outstanding finding) re-verified and re-stamped spec/attestation.yaml.
Drift is now clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ko.html

Brings README.html, README.md, README.ko.md, and docs/img/en/*.svg to parity with
the finalized README.ko.html, and reconciles post-merge counts across every README.

- README.html / README.md — English mirror of ko.html: enterprise hero, the
  project-map (knowledge-graph) section with the live galaxy GIF + colour legend +
  clad-graph-serve launch guide, every section, locked numbers
- README.ko.md — Korean-markdown mirror (plain-declarative voice, docs/img/ko refs)
- docs/img/en/*.svg (8) — English of the finalized ko diagrams (identical geometry/
  style, terse English to fit fixed boxes) + docs/img/en/graph.gif
- counts reconciled everywhere: 40 detectors · 199 features (195 done) · 1664 tests ·
  169 test files · v0.7.0 · 2026-07 (was 196/192, 39, 1630, 2026-06 in places)
- ko.html: card "언제든 추적할 수 있다", Status 196/192 → 199/195, date → 2026-07
- content-accuracy carried to English: SoD "aligns with" (not "maps to") EU AI Act/SOX,
  Tier-B = project-context.md, runner examples = pure executors, gate 3/9/15 by cost,
  knowledge graph = traceability not correctness

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 done features own README/diagram modules touched by the lockstep commit; a GREEN
strict pre-push gate (all other stages pass) re-verified and re-stamped
spec/attestation.yaml. Drift is clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ped output at 64 KiB

runGraphExportCommand wrote the rendered graph to stdout then called process.exit(0)
on the next line; on a pipe, stdout.write is async, so exit killed the process before
the ~64 KiB OS pipe buffer drained — `clad graph export --format json | jq` truncated
at exactly 65536 B (this repo's payload is ~285 KiB). File / --out / serve / MCP
(synchronous or HTTP) were unaffected. Exit from the write callback instead; same
prophylactic fix for `graph stats`. Found by the pre-PR empirical verification sweep.

- src/cli/graph.ts: process.stdout.write(x, () => process.exit(0)) at both stdout sites
- tests/cli/graph-export-pipe.test.ts: spawns the CLI through a pipe, asserts the full
  >64 KiB JSON arrives and parses
- count reconciliation from the new test file: tests 1664→1665, test_files 169→170
  across the 4 READMEs + spec.yaml inventory; dist rebuilt; attestation re-stamped

Verified: `clad graph export --format json | jq` → 199 feature nodes (was truncated);
npm test 1665/1665 GREEN; pre-push strict gate green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ersona permission separation")

The multi-agent diagram title used "페르소나 권한 분리" / "Persona Separation of Duties",
but the READMEs call these agents (not personas) and the concept is role/duty separation,
not permissions — and the diagram's own subtitle already says segregation-of-duties.
Aligned the title to that vocabulary:
- ko: "페르소나 권한 분리" → "에이전트 역할 분리"
- en: "Persona Separation of Duties" → "Agent Separation of Duties"
- ko/en multi-agent.svg (title + a11y) + all four README alt texts; attestation re-stamped

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ity)

The two markdown READMEs mirrored the HTML too literally (layout <table>s, an
inline-styled Status block GitHub strips to bare text, centered <p>+<br> prose).
Rewrote them to native markdown — text verbatim, format only:
- layout 3-col tables → stacked sections / bullet trios (the hero hook,
  before/after/record, see/ask/measure)
- Status styled-table → one clean 5-col markdown table
- detector HTML table → markdown table; centered body prose → left-aligned + a blockquote
- one extra blank line before each main (##) section heading
- fixed a pre-existing KO detector-table count (spec↔test 5→6, so the rows sum to 40)
  and added the missing "capability 6개" to the KO Status footnote (EN parity)

HTML siblings (README.html, README.ko.html) and all diagrams left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ing midnight

spec.yaml's inventory.last_synced is a module of F-5b9f9f / F-32b1e0 / F-d6b93648,
so when sync/check advances it to the current date, those features' attested tree-
hash goes stale. The branch was attested on 06-30; CI ran on 07-01 UTC and re-stamped
last_synced to 07-01, so the self-drift gate (`clad check --tier=pre-commit --strict`)
reported STALE_ATTESTATION. Re-attested against today so the committed spec.yaml +
attestation agree with a same-day CI run.

Follow-up (pre-existing, separate): last_synced should not churn attestation — exclude
it from the module hash, or stop writing it from check/build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The before → verify → record loop was narrated ~5× at different altitudes. Cut the
cross-section redundancy and tightened wordy prose (~-93 lines; larger in rendered prose):
- "how it works with host LLM": dropped the intro + the entire After/Record subsections
  (re-told downstream in Gate / Detector / "done is earned") → one pointer line; kept Before
- removed the duplicate "no commands to memorize" line, the tagline-blurb flourish, the
  inline 9-stage list, and restated cost-split / colour-legend / Authority-column / ecosystem tails

The "기업이~ / To trust AI" tagline + its 3 trust cards are preserved byte-for-byte (core
hook, per maintainer). Numbers, honesty notes, diagrams, commands, and Docs links all intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@qwerfunch qwerfunch merged commit 1a6f868 into develop Jul 1, 2026
1 check passed
qwerfunch added a commit that referenced this pull request Jul 1, 2026
* feat(drift): UNVERIFIED_AC multi-framework test_ref↔testcase matching (closes #203) (#204)

* feat(drift): UNVERIFIED_AC — verify test_refs ran and passed via JUnit XML (F-96700032)

UNTESTED_AC only checks that a done AC's test_refs EXIST on disk — an empty
file, a test.skip, or a failing test all satisfied the gate, so the chain
stopped at "AC → named file", not "AC → observed pass". UNVERIFIED_AC closes
that gap when a JUnit XML report is available.

- New detector UNVERIFIED_AC (src/stages/detectors/unverified-ac.ts) + a pure
  regex JUnit parser (src/stages/junit-report.ts, no XML dependency, mirroring
  the coverage-XML approach). Report path comes from gate.test_report in
  .cladding/config.yaml or a conventional default; failing/errored or
  only-skipped test_refs are an error, absent ones a warn (--strict promotes).
- Graceful by default: no report present → emits nothing, leaving UNTESTED_AC's
  existence check as the baseline, so non-JUnit projects are unaffected.
- Registered in detectors/index.ts (count auto-recomputed 37→38); catalog +
  self-consistency detector-count claims bumped to 38.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(drift): UNVERIFIED_AC multi-framework test_ref↔testcase matching (F-d980359c)

UNVERIFIED_AC's matcher was effectively vitest-only — it keyed every testcase
by `classname` and assumed that was a file path. pytest (`tests.test_foo`),
Java/Kotlin FQCN (`com.example.FooTest`), and `file=`-attribute emitters never
matched, so a passing test read as `absent` (a false positive under --strict)
and a real fail/skip was mis-reported as "did not run".

- parse: index each testcase under every path-shaped key it yields — the
  `file=` attribute, the `classname` as-is, and a dot→slash conversion of a
  dotted (no-separator) classname — sharing one accumulator (no double count).
- lookup: extension-agnostic exact/suffix match (`FooTest` ↔ `FooTest.kt`).
- detector: confident-or-degrade — a report with no path-like keys (e.g. jest
  describe-title classnames) is unmappable, so emit nothing instead of flooding
  false `absent` findings (preserves the low-false-positive contract).

Measured A/B (OLD = classname-only): correct verdicts across a
vitest/pytest/Kotlin/jest matrix 2/8 → 8/8; parse cost on a 10k-case report
+1.6ms (vitest) … +7.8ms (pytest) per gate run — noise vs the ~50s gate.

Blind-authored tests (tests/stages/junit-multiframework.test.ts) cover all 5
ACs. Existing UNVERIFIED_AC tests stay green (backward compatible).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* feat(ears): add the 6th canonical EARS pattern `complex` (while + when) (F-9d168287) (#208)

src/spec/ears.ts implemented 5 of 6 EARS patterns; the 6th — `complex`, a
precondition combined with a trigger ("While A, when B, the system shall X.")
— was missing, and the validator keyed on the first trigger word only, so a
multi-clause `While … when …` requirement either failed validation or was
forced into a single-keyword bucket, silently dropping the trigger clause.

- `EarsPattern` gains `complex`; checkEarsShape validates BOTH clauses (leading
  `while` precondition + a `when` trigger) and names the missing one, preserving
  the precondition→trigger relationship EARS exists to capture.
- Purely additive: the existing five patterns validate exactly as before.
- `complex` mirrored across every enum site in lockstep (types.ts,
  spec/schema.json ac.ears + always_ears, new.ts, MCP server enum,
  spec-conformance message) so HARNESS_INTEGRITY stays green.

Blind-authored tests (tests/spec/ears-complex.test.ts, 14) cover the new
pattern, the missing-clause messages, backward-compat for all 5 prior patterns,
and the schema-mirror.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* Knowledge graph — see, ask, and measure how spec, code, tests, and docs connect (#222)

* feat(graph): reverse-edge index (backlinks) — F-ee47fc2b

Derive three reverse maps from the spec's forward edges, memoized per
Spec instance via a WeakMap (no Spec mutation, 0 bytes on disk):
- dependents:        featureId -> direct dependents (inverse depends_on)
- moduleOwners:      module path -> owning feature ids (many-to-many)
- testRefCitations:  test path -> citing feature ids (anchor-stripped)

The backlink seam the graph layer (blast-radius queries, doc linking,
exports) reuses. pruneToFeature only walked depends_on upward; this is
the reverse direction. Tests authored impl-blind (4/4 green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): blast-radius impact query — F-7794a6bc

buildImpactSlice + clad_get_impact (MCP) + clad impact (CLI): the
backward complement of clad_get_context. Resolve a feature id/slug or a
module path (fanning out through the many-to-many module owners) and walk
the reverse-index dependents to return the blast radius — impacted
features, scenarios at risk, the deduped regression test set, and the
modules in the radius. Bounded by optional depth, deterministic.

Live: `clad impact src/spec/load.ts` → 4 owners, 111 impacted features,
115 regression tests. Logic tested impl-blind (4/4); MCP integration
tested (clad_get_impact). Glossary + verb-list updated for the new surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): doc graph — doc↔spec / doc↔doc links + integrity — F-ee5f643e

extractDocReferences scans docs/**/*.md (excluding fixture/benchmark
dirs, stripping code spans) for F-id references and resolved relative
.md links; clad sync materialises spec/_doc-links.yaml (Tier C, the
greppable "which docs explain feature X" index). New DOC_LINK_INTEGRITY
detector (#38): a dead relative .md link is an error; a scoped doc's
unresolved F-id is a warn. Per-file `clad-doc-links: ignore` opt-out for
teaching docs that use illustrative ids (spec-ids-multi-dev, ssot-testing).

The "all documents connected, always current" half of the graph, made
mechanical. Detector green on cladding-self (scoping = zero false-RED).
Logic + detector tested impl-blind (6/6). Detector count 37→38 across
prose claims; A/B reports regenerated for the new info finding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): knowledge-graph export + hub stats — F-569f4b37

src/graph/: buildGraph materialises ONE typed graph (feature/module/test/
scenario/capability/doc nodes; depends_on/touches/covers/binds/implements/
references/links edges) from spec + reverse-index + doc-links. Renderers:
mermaid + DOT + JSON (stdout) and an Obsidian vault (one note per node with
[[wikilinks]] + Backlinks) — see the graph in best-in-class viewers, no
bespoke UI. `clad graph stats` ranks hubs by degree (what's load-bearing).
`--focus <q> --depth N` exports a legible neighborhood subgraph.

Declared a `graph` foundation layer in architecture.yaml (pure spec reader).
Live: 706 nodes / 1247 edges; top hub src/cli/clad.ts (degree 33). Pure
functions tested impl-blind (6/6). Verb count 19→20; glossary + SVG diagram
counts (→38 detectors) updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(graph): link knowledge-graph capability + CHANGELOG + refresh indexes

- spec/capabilities.yaml: new `knowledge-graph` capability (Tier B) grouping
  the four graph features (F-ee47fc2b, F-7794a6bc, F-ee5f643e, F-569f4b37).
- CHANGELOG [Unreleased]: knowledge-graph entry, framed as traceability/
  retrieval (NOT correctness — honest per the A/B record).
- clad sync refresh: spec/index.yaml, spec/_doc-links.yaml, attestation re-stamp.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): bespoke HTML viewer — SSoT 4-tier colors + slug labels — F-02343cd1

The graph, seen our own way (more than the spec asked):
- model: GraphNode.tier (A/B/C/D) classified deterministically — features/
  scenarios=A, capabilities=B, docs by first-line `Cladding · Tier X` banner
  (+ known-filename fallback), modules/tests=code (no tier). Feature labels now
  show the slug (slug ?? title ?? id); full title kept as `detail` for hover.
- render: getTierColor + getTierLegend; tier flows into json, obsidian
  frontmatter, and mermaid (per-tier classDef coloring — bonus).
- viewer-shell + src/graph/viewer/{app.js,styles.css}: toHtmlShell emits ONE
  self-contained, offline, double-clickable .html — a hand-rolled, zero-dep
  canvas force-directed renderer (NOT a vendored 80KB lib: truest to the
  zero-dep ethos, full control, ~no supply-chain surface). Tier colors, slug
  labels, status opacity, degree-sized hubs, a sidebar (search, kind+tier
  filters with counts, tier legend, Calm/Live, theme, labels), hover-
  neighborhood, click-to-pin, zoom/pan, localStorage layout persistence.
- `clad graph export --format html --out f.html` (mandatory --out). Build copies
  viewer assets to dist/viewer/ (schema.json precedent). eslint-ignored.

Live: 710 nodes / 1259 edges → 213KB self-contained .html, byte-deterministic,
zero external requests. Tests authored impl-blind (3/3). gate GREEN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): live graph view — clad graph serve + clad_get_graph — F-64a5c159

The graph as a LIVE view, not a re-exported snapshot (the user's reframe:
the graph is a pure derivation of the spec, so build the view once and let
it auto-update as development proceeds).

- src/cli/graph-serve.ts: `clad graph serve [--port]` — a stdlib node:http
  server (zero deps). GET / serves the viewer (with an injected SSE reload
  snippet), GET /graph.json recomputes buildGraph on EVERY request (always
  current — no stale-trap), GET /events is a text/event-stream channel.
  node:fs.watch on spec/ + docs/ broadcasts a debounced refresh → open
  browsers auto-reload. Hardened: headersSent-guarded error path +
  closeAllConnections so it shuts down cleanly (and survives EventSource
  disconnects). SSE keep-alive every 30s.
- src/serve/server.ts: clad_get_graph MCP tool — agents read the live
  (optionally focused) graph in one call; never stale.

Live: GET /graph.json reflects the current spec (713 nodes after this
feature landed). Endpoints + broadcast tested impl-blind (2/2); clad_get_graph
tested over MCP. glossary + TOOL_NAMES updated. gate GREEN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(graph): link viewer + live-serve features to capability + CHANGELOG

- spec/capabilities.yaml: knowledge-graph now also owns the HTML viewer
  (F-02343cd1) and live serve (F-64a5c159).
- CHANGELOG [Unreleased]: the SSoT-tier-colored viewer + the live auto-updating
  serve, framed plainly (see/navigate the structure, not a correctness check).
- clad sync refresh: index.yaml, _doc-links.yaml, attestation re-stamp.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(graph): viewer canvas was unsized (blank graph) — size to viewport + sidebar-aware fit

The canvas had no CSS width/height, so it fell back to the intrinsic 300×150
and sat hidden behind the 264px sidebar — the graph area rendered blank.
Size the canvas to 100vw/100vh and offset fit() by the sidebar width so the
graph centers in the visible area. Verified headless: nodes draw (ctx.arc
fires 31k× across the settle frames on the 713-node self-graph).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): viewer overhaul — radial galaxy + always-on ambient + bloom — F-8234ec3c

Make it extreme, and alive at rest (the user's ask):
- LAYOUT: a degree-weighted radial galaxy. High-degree hubs are pulled toward
  the centre, low-degree leaves toward the rim; charge spreads angularly,
  springs cluster the connected — the graph gathers into one circular galaxy
  with a bright load-bearing core. Verified on the real 717-node graph: 0
  NaN/Inf, hub (deg 35) at dist 412 vs median 987 (central).
- ALIVE AT REST: ambient is now the default — a slow global rotation + node
  breathing + flowing edge particles + hub glow pulse, all O(n)/O(edges) draw
  (rotation is a free transform; physics stays burst-only). "Calm" freezes it.
- LOOK: additive ("lighter") bloom pass so overlapping hubs build a glowing
  core, over a deep-space radial-gradient background. Click-to-focus (persistent
  neighborhood highlight + smooth recenter), smooth view lerp, upright labels.

Plus a headless render test (tests/graph/viewer-render.test.ts): stubs canvas/
document/window, runs the real app.js, and asserts it draws nodes, keeps the
ambient loop alive, and settles to finite positions with the hub central —
guarding the "blank canvas" / NaN-blowup regressions deterministically in-gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(graph): link galaxy viewer feature to knowledge-graph capability

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): Obsidian-grade viewer + live conformance heal (killer) — F-04f50847, F-af45042a

Re-architect the viewer to Obsidian quality, and add the killer that only a
spec↔code-connected tool can do.

Obsidian-grade (F-04f50847):
- Continuous low-alpha simulation with an alphaTarget thermostat: dragging a node
  reheats so connected nodes follow elastically (real tension); HOVER pauses the
  sim (motion freezes under the cursor); release decays to rest. Frame-time
  normalized, slow/calm. Removed the global rotation + edge particles + the
  forced radial-by-degree layout (→ organic center+charge+link balance).
- Four force sliders (중심 장력 / 반발력 / 링크 장력 / 링크 거리) live-bound + persisted.
- nodeColor separates all classes: tiers A/B/C/D distinct hues + module(orange)/
  test(green)/doc(pink) distinct (were all gray).

KILLER — live conformance heal (F-af45042a):
- src/stages/graph-health.ts: nodeHealth() runs cladding's drift detectors and
  maps each finding to its graph node (path→module/test/doc, F-id→feature),
  worst-severity per node. (Lives in stages/: graph→stages is forbidden,
  stages→graph is fine.)
- clad graph serve: GET /health.json (live, watch-refreshed); the viewer overlays
  problem nodes (error=red pulse / warn=amber) over the pretty default + an
  in-sync% pill, and heals smoothly on SSE refresh (viewer self-wires events;
  the reload injection is gone). Static export embeds a stamped snapshot.
- Verified live: /health.json flags the exact features whose modules drifted from
  attestation; they heal green once the gate re-attests.

Galaxy viewer (F-8234ec3c) archived — superseded by F-04f50847 (radial layout,
global rotation, particles replaced; bloom + click-focus carried forward).
Headless tests rewritten for the new behavior (hover-pause, drag-reheat, color,
finite) + a health-mapper test. 1534 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(graph): re-attest after viewer build (heal stale attestation)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(graph): viewer aesthetics — collision spacing, visible edges, no muddy bloom

The dense overlapping 'confetti' is now a breathing web: a collision pass
hard-separates overlapping nodes (0 overlapping pairs on the 717-node graph,
was a clump), stronger repel + smaller nodes spread it (span ~1350). Edges are
colored-by-source and actually visible (the structure reads); the additive
bloom (which washed the centre muddy/white) is removed; nodes get a thin
bg-colored ring so touching nodes stay crisp; health is a clean pulsing ring
not a blob. Force defaults retuned for an even, Obsidian-like distribution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): WebGL stellar viewer + working-set context tooling (F-77f7ead0, F-06dfdad6, F-d6b93648)

Knowledge-graph session work on feature/ssot-knowledge-graph:

- webgl-stellar-viewer (F-77f7ead0): real three.js + UnrealBloom 3D galaxy over
  the SSoT graph (semantic hue × degree luminosity) + live drift-health overlay,
  esbuild-bundled offline. Adds a 'skill' node kind (SKILL.md distinct from code).
  Archives the 2D canvas viewer (F-04f50847, superseded); swaps F-af45042a module.
- working-set-assembler (F-06dfdad6): clad_get_working_set — one token-budgeted,
  code-bearing payload (focus + module code + forward needs + backward breaks +
  verify + budget). ~5-8x smaller than reading shard+files; clad_get_context frozen.
- graph-context-wiring (F-d6b93648): PostToolUse auto impact-card after source edits
  + ai_hints/persona nudge so the working set is actually used.

All three: strict gate GREEN + attested; 1603 tests pass; tsc/eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): context-efficiency tooling + the depends_on gap fix (F-96250595, F-2be3e3bb, F-15999130, F-16138071)

Four features that make cladding's knowledge graph populated + measurable, on
feature/ssot-knowledge-graph. The arc: 4 A/Bs found the graph tools NULL on agent
correctness — then re-framing to the real goal (search/context efficiency, not
"smarter agents") + fixing the empty-graph root cause turned it positive.

- iterative-impact-slice (F-96250595): buildIterativeImpactSlice — seed→widen→stop
  (coverage/exhaustion/marginal-yield), self-describing depth+stoppedBy+coverage;
  fixes the fixed-depth-1 narrow-miss. Calibrated on real graph data (2 sims).
- infer-depends-on (F-2be3e3bb): clad infer-deps reconstructs feature depends_on
  from the code import graph — the load-bearing graph edge cladding PRODUCED nowhere
  (vapt shipped 0 edges → empty graph → the real cause of the A/B NULLs). vapt 0→698.
- inferable-depends-on detector (F-15999130): INFERABLE_DEPENDS_ON — single info
  finding (never blocks, even strict) surfacing the empty-graph gap so infer-deps
  isn't a latent never-run tool. Detector count 38→39.
- graph-efficiency-measure (F-16138071): clad measure — deterministic per-feature
  search/context efficiency (no agent, no NULL risk). vapt: working-set 4.1x smaller
  context than naive (shard+all modules); dependency radius + regression set resolved
  for free. The goal axis, finally quantified positive.

Honest framing throughout (docs/ab-evaluation/*.md): the graph does NOT make a capable
agent more correct/cheaper (it greps regardless — NULL x4); its value is small context
+ explicit blast radius + known regression set + a queryable dependency graph, for
humans and tools that use it. infer-deps writes nothing (reviewable suggestions only).

All gated: 1624 tests GREEN, tsc/eslint clean, strict gate + attested per feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(graph): post-audit hygiene — clarify efficiency-report stats + drop dead StopReason

Read-only adversarial audit of the session found the work correct (4 features
conform, tests honest, claims reproduce, no regressions). Two low-severity hygiene
fixes, zero functional impact:

- docs/ab-evaluation/case-efficiency-measurement.md: the table wrote "3,028 tok vs
  14,442 tok = 4.1x (median)" on one line, which reads as a contradiction (14442/3028
  ≈ 4.8). Both numbers are correct but are DIFFERENT statistics — 4.1x is the median
  of per-feature naive÷slice ratios; 3,028/14,442 are independent medians of slice
  and naive sizes. Split them + added a note so the report can't be misread. (The
  audit verdict marked 4.1x "VERIFIED" and missed this; caught by hand arithmetic.)
- src/optimizer/iterative-slice.ts: removed 'token-budget' from the StopReason union
  — it was declared but never returned (budget overflow stops at max-depth first).
  Synced the test's ALLOWED_STOPS list.

1624 tests GREEN, tsc/eslint clean, strict gate re-attested (F-96250595).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(infer-deps): flag dynamic-import files for honest recall (F-0f2984d0)

The final read-only graph-traversal test on the real doverunner-vapt project
measured inference precision ~100% but recall ~70-75% — the largest remaining gap
being dynamic/runtime imports (importlib.import_module, __import__, getattr-based,
require(<expr>)) that static regex extraction cannot see (e.g. catalog/tool_inventory.py).

Rather than silently under-report those edges, inferDependsOn now collects such
module files into a sorted dynamicImportFiles list so a maintainer knows exactly
which files to review by hand. This is an honest-recall surface, NOT a precision
change — the static edges from the same files are still inferred unchanged (additive).
clad infer-deps prints dynamic_import_files.

Verified: cladding-self flags scripts/build.mjs (require). blind tests 3/3 in a
separate file (existing 6 untouched); 1627 tests GREEN; tsc/eslint clean; strict
gate re-attested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): color system — hue=kind only, grouped spec/code/test/docs (F-5b188856)

The viewer's node colors had no clear basis: semanticHue let tier win over kind,
so one hue tried to encode two axes and collided — feature and scenario both
rendered blue, tier-C teal ≈ skill turquoise, and the sidebar showed the same
blue labeled both "Spec · sealed" (tier) and "feature" (kind). Tier is derivable
from kind, so encoding both was redundant.

Hue now encodes KIND only. semanticHue ignores tier; tier moves to the sidebar
filter + tooltip. KIND_COL becomes a grouped, simulation-verified palette
(all Y≥125 bloom floor, colorblind 4-group separation, hub-whitening distinctness):
  SPEC = blue family (feature/scenario/capability)
  module = orange (anchor) · test = green (anchor)
  DOCS = pink family (doc/skill) — skill is SKILL.md, a document, moved out of the
  old code-adjacent turquoise.

Sidebar: one color legend grouped into spec/code/test/docs zones; the SSoT tier
section is a swatch-less filter (checkbox + label only, no empty box). Tier labels
simplified to plain words (Spec / Design / Derived / Audit), and code nodes display
as "code" in the viewer (the spec's `modules:` data model is unchanged — display
label only).

Honest residuals (accepted, user-preferred anchors): module orange sits near the
0-45° health-burn arc, disambiguated by the burn's 2.2-3× pulse vs a static node;
orange↔green is the textbook red-green colorblind pair but is conceptually adjacent
(code↔test) with a luminance backstop.

1630 tests GREEN; tsc/eslint clean; strict gate re-attested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(readme-ko): graph section + live GIF, flat blue-token diagrams, content-accuracy pass

README.ko.html:
- enterprise trust/trace/scale hero; new project-map (graph) section with a live
  galaxy GIF, colour legend, and a clad-graph-serve launch guide
- refreshed numbers (39 detectors / 1630 tests / 196 features, 192 done / v0.7.0)
- unified plain-declarative voice; blue-primary emphasis (green = pass/success only)
- accurate 4-tier SSoT table (intent defined by humans, authored by the LLM per EARS)

docs/img/ko/*.svg (8): unified flat blue-token design system (no shadow/pastel/accent
bars, no version stamps); every claim verified against code — gate triggers 3/9/15 by
cost, authorship human-defines/LLM-writes, segregation-of-duties "aligns with" (not
"maps to") EU AI Act/SOX, Tier-B = project-context.md (not ai_hints), runner examples
= pure executors

docs/img/ko/graph.gif: 880px/8fps live-graph recording (6.9MB)
.gitignore: ignore *.mov (local screen recordings)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(attest): re-attest after develop merge — refresh verified tree-hashes

The merge moved 66 done features' module trees. A GREEN strict pre-push gate
(type/lint/unit/coverage/deliverable-smoke all pass; STALE_ATTESTATION was the
only outstanding finding) re-verified and re-stamped spec/attestation.yaml.
Drift is now clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(readme): sync all 4 READMEs + English diagrams to the finalized ko.html

Brings README.html, README.md, README.ko.md, and docs/img/en/*.svg to parity with
the finalized README.ko.html, and reconciles post-merge counts across every README.

- README.html / README.md — English mirror of ko.html: enterprise hero, the
  project-map (knowledge-graph) section with the live galaxy GIF + colour legend +
  clad-graph-serve launch guide, every section, locked numbers
- README.ko.md — Korean-markdown mirror (plain-declarative voice, docs/img/ko refs)
- docs/img/en/*.svg (8) — English of the finalized ko diagrams (identical geometry/
  style, terse English to fit fixed boxes) + docs/img/en/graph.gif
- counts reconciled everywhere: 40 detectors · 199 features (195 done) · 1664 tests ·
  169 test files · v0.7.0 · 2026-07 (was 196/192, 39, 1630, 2026-06 in places)
- ko.html: card "언제든 추적할 수 있다", Status 196/192 → 199/195, date → 2026-07
- content-accuracy carried to English: SoD "aligns with" (not "maps to") EU AI Act/SOX,
  Tier-B = project-context.md, runner examples = pure executors, gate 3/9/15 by cost,
  knowledge graph = traceability not correctness

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(attest): re-attest after all-READMEs lockstep

5 done features own README/diagram modules touched by the lockstep commit; a GREEN
strict pre-push gate (all other stages pass) re-verified and re-stamped
spec/attestation.yaml. Drift is clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(graph): flush stdout before exit — clad graph export truncated piped output at 64 KiB

runGraphExportCommand wrote the rendered graph to stdout then called process.exit(0)
on the next line; on a pipe, stdout.write is async, so exit killed the process before
the ~64 KiB OS pipe buffer drained — `clad graph export --format json | jq` truncated
at exactly 65536 B (this repo's payload is ~285 KiB). File / --out / serve / MCP
(synchronous or HTTP) were unaffected. Exit from the write callback instead; same
prophylactic fix for `graph stats`. Found by the pre-PR empirical verification sweep.

- src/cli/graph.ts: process.stdout.write(x, () => process.exit(0)) at both stdout sites
- tests/cli/graph-export-pipe.test.ts: spawns the CLI through a pipe, asserts the full
  >64 KiB JSON arrives and parses
- count reconciliation from the new test file: tests 1664→1665, test_files 169→170
  across the 4 READMEs + spec.yaml inventory; dist rebuilt; attestation re-stamped

Verified: `clad graph export --format json | jq` → 199 feature nodes (was truncated);
npm test 1665/1665 GREEN; pre-push strict gate green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(multi-agent): retitle diagram to "agent role separation" (was "persona permission separation")

The multi-agent diagram title used "페르소나 권한 분리" / "Persona Separation of Duties",
but the READMEs call these agents (not personas) and the concept is role/duty separation,
not permissions — and the diagram's own subtitle already says segregation-of-duties.
Aligned the title to that vocabulary:
- ko: "페르소나 권한 분리" → "에이전트 역할 분리"
- en: "Persona Separation of Duties" → "Agent Separation of Duties"
- ko/en multi-agent.svg (title + a11y) + all four README alt texts; attestation re-stamped

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(readme): make README.md + README.ko.md markdown-native (readability)

The two markdown READMEs mirrored the HTML too literally (layout <table>s, an
inline-styled Status block GitHub strips to bare text, centered <p>+<br> prose).
Rewrote them to native markdown — text verbatim, format only:
- layout 3-col tables → stacked sections / bullet trios (the hero hook,
  before/after/record, see/ask/measure)
- Status styled-table → one clean 5-col markdown table
- detector HTML table → markdown table; centered body prose → left-aligned + a blockquote
- one extra blank line before each main (##) section heading
- fixed a pre-existing KO detector-table count (spec↔test 5→6, so the rows sum to 40)
  and added the missing "capability 6개" to the KO Status footnote (EN parity)

HTML siblings (README.html, README.ko.html) and all diagrams left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(attest): re-stamp at 2026-07-01 — CI stale from last_synced crossing midnight

spec.yaml's inventory.last_synced is a module of F-5b9f9f / F-32b1e0 / F-d6b93648,
so when sync/check advances it to the current date, those features' attested tree-
hash goes stale. The branch was attested on 06-30; CI ran on 07-01 UTC and re-stamped
last_synced to 07-01, so the self-drift gate (`clad check --tier=pre-commit --strict`)
reported STALE_ATTESTATION. Re-attested against today so the committed spec.yaml +
attestation agree with a same-day CI run.

Follow-up (pre-existing, separate): last_synced should not churn attestation — exclude
it from the module hash, or stop writing it from check/build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(readme): trim prose for concision across all 4 READMEs

The before → verify → record loop was narrated ~5× at different altitudes. Cut the
cross-section redundancy and tightened wordy prose (~-93 lines; larger in rendered prose):
- "how it works with host LLM": dropped the intro + the entire After/Record subsections
  (re-told downstream in Gate / Detector / "done is earned") → one pointer line; kept Before
- removed the duplicate "no commands to memorize" line, the tagline-blurb flourish, the
  inline 9-stage list, and restated cost-split / colour-legend / Authority-column / ecosystem tails

The "기업이~ / To trust AI" tagline + its 3 trust cards are preserved byte-for-byte (core
hook, per maintainer). Numbers, honesty notes, diagrams, commands, and Docs links all intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(release): v0.7.0 — version bump (9 sites) + lock + CHANGELOG + rebuild

- version-bump 0.7.0 across the 9 HARNESS_INTEGRITY sites (package.json, marketplace.json,
  both plugin.json, gemini-extension.json, src/cli/clad.ts, src/serve/server.ts,
  tests/cli/clad.test.ts, spec.yaml); package-lock refreshed via npm install
- CHANGELOG: [Unreleased] → [0.7.0] — 2026-07-01 — Knowledge Graph
- dist + plugin mirrors rebuilt; attestation re-stamped
- 1665/1665 tests green; pre-push strict gate green

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Sungju <yuyu04@naver.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
qwerfunch added a commit that referenced this pull request Jul 2, 2026
* feat(drift): UNVERIFIED_AC multi-framework test_ref↔testcase matching (closes #203) (#204)

* feat(drift): UNVERIFIED_AC — verify test_refs ran and passed via JUnit XML (F-96700032)

UNTESTED_AC only checks that a done AC's test_refs EXIST on disk — an empty
file, a test.skip, or a failing test all satisfied the gate, so the chain
stopped at "AC → named file", not "AC → observed pass". UNVERIFIED_AC closes
that gap when a JUnit XML report is available.

- New detector UNVERIFIED_AC (src/stages/detectors/unverified-ac.ts) + a pure
  regex JUnit parser (src/stages/junit-report.ts, no XML dependency, mirroring
  the coverage-XML approach). Report path comes from gate.test_report in
  .cladding/config.yaml or a conventional default; failing/errored or
  only-skipped test_refs are an error, absent ones a warn (--strict promotes).
- Graceful by default: no report present → emits nothing, leaving UNTESTED_AC's
  existence check as the baseline, so non-JUnit projects are unaffected.
- Registered in detectors/index.ts (count auto-recomputed 37→38); catalog +
  self-consistency detector-count claims bumped to 38.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(drift): UNVERIFIED_AC multi-framework test_ref↔testcase matching (F-d980359c)

UNVERIFIED_AC's matcher was effectively vitest-only — it keyed every testcase
by `classname` and assumed that was a file path. pytest (`tests.test_foo`),
Java/Kotlin FQCN (`com.example.FooTest`), and `file=`-attribute emitters never
matched, so a passing test read as `absent` (a false positive under --strict)
and a real fail/skip was mis-reported as "did not run".

- parse: index each testcase under every path-shaped key it yields — the
  `file=` attribute, the `classname` as-is, and a dot→slash conversion of a
  dotted (no-separator) classname — sharing one accumulator (no double count).
- lookup: extension-agnostic exact/suffix match (`FooTest` ↔ `FooTest.kt`).
- detector: confident-or-degrade — a report with no path-like keys (e.g. jest
  describe-title classnames) is unmappable, so emit nothing instead of flooding
  false `absent` findings (preserves the low-false-positive contract).

Measured A/B (OLD = classname-only): correct verdicts across a
vitest/pytest/Kotlin/jest matrix 2/8 → 8/8; parse cost on a 10k-case report
+1.6ms (vitest) … +7.8ms (pytest) per gate run — noise vs the ~50s gate.

Blind-authored tests (tests/stages/junit-multiframework.test.ts) cover all 5
ACs. Existing UNVERIFIED_AC tests stay green (backward compatible).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* feat(ears): add the 6th canonical EARS pattern `complex` (while + when) (F-9d168287) (#208)

src/spec/ears.ts implemented 5 of 6 EARS patterns; the 6th — `complex`, a
precondition combined with a trigger ("While A, when B, the system shall X.")
— was missing, and the validator keyed on the first trigger word only, so a
multi-clause `While … when …` requirement either failed validation or was
forced into a single-keyword bucket, silently dropping the trigger clause.

- `EarsPattern` gains `complex`; checkEarsShape validates BOTH clauses (leading
  `while` precondition + a `when` trigger) and names the missing one, preserving
  the precondition→trigger relationship EARS exists to capture.
- Purely additive: the existing five patterns validate exactly as before.
- `complex` mirrored across every enum site in lockstep (types.ts,
  spec/schema.json ac.ears + always_ears, new.ts, MCP server enum,
  spec-conformance message) so HARNESS_INTEGRITY stays green.

Blind-authored tests (tests/spec/ears-complex.test.ts, 14) cover the new
pattern, the missing-clause messages, backward-compat for all 5 prior patterns,
and the schema-mirror.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* Knowledge graph — see, ask, and measure how spec, code, tests, and docs connect (#222)

* feat(graph): reverse-edge index (backlinks) — F-ee47fc2b

Derive three reverse maps from the spec's forward edges, memoized per
Spec instance via a WeakMap (no Spec mutation, 0 bytes on disk):
- dependents:        featureId -> direct dependents (inverse depends_on)
- moduleOwners:      module path -> owning feature ids (many-to-many)
- testRefCitations:  test path -> citing feature ids (anchor-stripped)

The backlink seam the graph layer (blast-radius queries, doc linking,
exports) reuses. pruneToFeature only walked depends_on upward; this is
the reverse direction. Tests authored impl-blind (4/4 green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): blast-radius impact query — F-7794a6bc

buildImpactSlice + clad_get_impact (MCP) + clad impact (CLI): the
backward complement of clad_get_context. Resolve a feature id/slug or a
module path (fanning out through the many-to-many module owners) and walk
the reverse-index dependents to return the blast radius — impacted
features, scenarios at risk, the deduped regression test set, and the
modules in the radius. Bounded by optional depth, deterministic.

Live: `clad impact src/spec/load.ts` → 4 owners, 111 impacted features,
115 regression tests. Logic tested impl-blind (4/4); MCP integration
tested (clad_get_impact). Glossary + verb-list updated for the new surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): doc graph — doc↔spec / doc↔doc links + integrity — F-ee5f643e

extractDocReferences scans docs/**/*.md (excluding fixture/benchmark
dirs, stripping code spans) for F-id references and resolved relative
.md links; clad sync materialises spec/_doc-links.yaml (Tier C, the
greppable "which docs explain feature X" index). New DOC_LINK_INTEGRITY
detector (#38): a dead relative .md link is an error; a scoped doc's
unresolved F-id is a warn. Per-file `clad-doc-links: ignore` opt-out for
teaching docs that use illustrative ids (spec-ids-multi-dev, ssot-testing).

The "all documents connected, always current" half of the graph, made
mechanical. Detector green on cladding-self (scoping = zero false-RED).
Logic + detector tested impl-blind (6/6). Detector count 37→38 across
prose claims; A/B reports regenerated for the new info finding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): knowledge-graph export + hub stats — F-569f4b37

src/graph/: buildGraph materialises ONE typed graph (feature/module/test/
scenario/capability/doc nodes; depends_on/touches/covers/binds/implements/
references/links edges) from spec + reverse-index + doc-links. Renderers:
mermaid + DOT + JSON (stdout) and an Obsidian vault (one note per node with
[[wikilinks]] + Backlinks) — see the graph in best-in-class viewers, no
bespoke UI. `clad graph stats` ranks hubs by degree (what's load-bearing).
`--focus <q> --depth N` exports a legible neighborhood subgraph.

Declared a `graph` foundation layer in architecture.yaml (pure spec reader).
Live: 706 nodes / 1247 edges; top hub src/cli/clad.ts (degree 33). Pure
functions tested impl-blind (6/6). Verb count 19→20; glossary + SVG diagram
counts (→38 detectors) updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(graph): link knowledge-graph capability + CHANGELOG + refresh indexes

- spec/capabilities.yaml: new `knowledge-graph` capability (Tier B) grouping
  the four graph features (F-ee47fc2b, F-7794a6bc, F-ee5f643e, F-569f4b37).
- CHANGELOG [Unreleased]: knowledge-graph entry, framed as traceability/
  retrieval (NOT correctness — honest per the A/B record).
- clad sync refresh: spec/index.yaml, spec/_doc-links.yaml, attestation re-stamp.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): bespoke HTML viewer — SSoT 4-tier colors + slug labels — F-02343cd1

The graph, seen our own way (more than the spec asked):
- model: GraphNode.tier (A/B/C/D) classified deterministically — features/
  scenarios=A, capabilities=B, docs by first-line `Cladding · Tier X` banner
  (+ known-filename fallback), modules/tests=code (no tier). Feature labels now
  show the slug (slug ?? title ?? id); full title kept as `detail` for hover.
- render: getTierColor + getTierLegend; tier flows into json, obsidian
  frontmatter, and mermaid (per-tier classDef coloring — bonus).
- viewer-shell + src/graph/viewer/{app.js,styles.css}: toHtmlShell emits ONE
  self-contained, offline, double-clickable .html — a hand-rolled, zero-dep
  canvas force-directed renderer (NOT a vendored 80KB lib: truest to the
  zero-dep ethos, full control, ~no supply-chain surface). Tier colors, slug
  labels, status opacity, degree-sized hubs, a sidebar (search, kind+tier
  filters with counts, tier legend, Calm/Live, theme, labels), hover-
  neighborhood, click-to-pin, zoom/pan, localStorage layout persistence.
- `clad graph export --format html --out f.html` (mandatory --out). Build copies
  viewer assets to dist/viewer/ (schema.json precedent). eslint-ignored.

Live: 710 nodes / 1259 edges → 213KB self-contained .html, byte-deterministic,
zero external requests. Tests authored impl-blind (3/3). gate GREEN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): live graph view — clad graph serve + clad_get_graph — F-64a5c159

The graph as a LIVE view, not a re-exported snapshot (the user's reframe:
the graph is a pure derivation of the spec, so build the view once and let
it auto-update as development proceeds).

- src/cli/graph-serve.ts: `clad graph serve [--port]` — a stdlib node:http
  server (zero deps). GET / serves the viewer (with an injected SSE reload
  snippet), GET /graph.json recomputes buildGraph on EVERY request (always
  current — no stale-trap), GET /events is a text/event-stream channel.
  node:fs.watch on spec/ + docs/ broadcasts a debounced refresh → open
  browsers auto-reload. Hardened: headersSent-guarded error path +
  closeAllConnections so it shuts down cleanly (and survives EventSource
  disconnects). SSE keep-alive every 30s.
- src/serve/server.ts: clad_get_graph MCP tool — agents read the live
  (optionally focused) graph in one call; never stale.

Live: GET /graph.json reflects the current spec (713 nodes after this
feature landed). Endpoints + broadcast tested impl-blind (2/2); clad_get_graph
tested over MCP. glossary + TOOL_NAMES updated. gate GREEN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(graph): link viewer + live-serve features to capability + CHANGELOG

- spec/capabilities.yaml: knowledge-graph now also owns the HTML viewer
  (F-02343cd1) and live serve (F-64a5c159).
- CHANGELOG [Unreleased]: the SSoT-tier-colored viewer + the live auto-updating
  serve, framed plainly (see/navigate the structure, not a correctness check).
- clad sync refresh: index.yaml, _doc-links.yaml, attestation re-stamp.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(graph): viewer canvas was unsized (blank graph) — size to viewport + sidebar-aware fit

The canvas had no CSS width/height, so it fell back to the intrinsic 300×150
and sat hidden behind the 264px sidebar — the graph area rendered blank.
Size the canvas to 100vw/100vh and offset fit() by the sidebar width so the
graph centers in the visible area. Verified headless: nodes draw (ctx.arc
fires 31k× across the settle frames on the 713-node self-graph).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): viewer overhaul — radial galaxy + always-on ambient + bloom — F-8234ec3c

Make it extreme, and alive at rest (the user's ask):
- LAYOUT: a degree-weighted radial galaxy. High-degree hubs are pulled toward
  the centre, low-degree leaves toward the rim; charge spreads angularly,
  springs cluster the connected — the graph gathers into one circular galaxy
  with a bright load-bearing core. Verified on the real 717-node graph: 0
  NaN/Inf, hub (deg 35) at dist 412 vs median 987 (central).
- ALIVE AT REST: ambient is now the default — a slow global rotation + node
  breathing + flowing edge particles + hub glow pulse, all O(n)/O(edges) draw
  (rotation is a free transform; physics stays burst-only). "Calm" freezes it.
- LOOK: additive ("lighter") bloom pass so overlapping hubs build a glowing
  core, over a deep-space radial-gradient background. Click-to-focus (persistent
  neighborhood highlight + smooth recenter), smooth view lerp, upright labels.

Plus a headless render test (tests/graph/viewer-render.test.ts): stubs canvas/
document/window, runs the real app.js, and asserts it draws nodes, keeps the
ambient loop alive, and settles to finite positions with the hub central —
guarding the "blank canvas" / NaN-blowup regressions deterministically in-gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(graph): link galaxy viewer feature to knowledge-graph capability

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): Obsidian-grade viewer + live conformance heal (killer) — F-04f50847, F-af45042a

Re-architect the viewer to Obsidian quality, and add the killer that only a
spec↔code-connected tool can do.

Obsidian-grade (F-04f50847):
- Continuous low-alpha simulation with an alphaTarget thermostat: dragging a node
  reheats so connected nodes follow elastically (real tension); HOVER pauses the
  sim (motion freezes under the cursor); release decays to rest. Frame-time
  normalized, slow/calm. Removed the global rotation + edge particles + the
  forced radial-by-degree layout (→ organic center+charge+link balance).
- Four force sliders (중심 장력 / 반발력 / 링크 장력 / 링크 거리) live-bound + persisted.
- nodeColor separates all classes: tiers A/B/C/D distinct hues + module(orange)/
  test(green)/doc(pink) distinct (were all gray).

KILLER — live conformance heal (F-af45042a):
- src/stages/graph-health.ts: nodeHealth() runs cladding's drift detectors and
  maps each finding to its graph node (path→module/test/doc, F-id→feature),
  worst-severity per node. (Lives in stages/: graph→stages is forbidden,
  stages→graph is fine.)
- clad graph serve: GET /health.json (live, watch-refreshed); the viewer overlays
  problem nodes (error=red pulse / warn=amber) over the pretty default + an
  in-sync% pill, and heals smoothly on SSE refresh (viewer self-wires events;
  the reload injection is gone). Static export embeds a stamped snapshot.
- Verified live: /health.json flags the exact features whose modules drifted from
  attestation; they heal green once the gate re-attests.

Galaxy viewer (F-8234ec3c) archived — superseded by F-04f50847 (radial layout,
global rotation, particles replaced; bloom + click-focus carried forward).
Headless tests rewritten for the new behavior (hover-pause, drag-reheat, color,
finite) + a health-mapper test. 1534 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(graph): re-attest after viewer build (heal stale attestation)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(graph): viewer aesthetics — collision spacing, visible edges, no muddy bloom

The dense overlapping 'confetti' is now a breathing web: a collision pass
hard-separates overlapping nodes (0 overlapping pairs on the 717-node graph,
was a clump), stronger repel + smaller nodes spread it (span ~1350). Edges are
colored-by-source and actually visible (the structure reads); the additive
bloom (which washed the centre muddy/white) is removed; nodes get a thin
bg-colored ring so touching nodes stay crisp; health is a clean pulsing ring
not a blob. Force defaults retuned for an even, Obsidian-like distribution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): WebGL stellar viewer + working-set context tooling (F-77f7ead0, F-06dfdad6, F-d6b93648)

Knowledge-graph session work on feature/ssot-knowledge-graph:

- webgl-stellar-viewer (F-77f7ead0): real three.js + UnrealBloom 3D galaxy over
  the SSoT graph (semantic hue × degree luminosity) + live drift-health overlay,
  esbuild-bundled offline. Adds a 'skill' node kind (SKILL.md distinct from code).
  Archives the 2D canvas viewer (F-04f50847, superseded); swaps F-af45042a module.
- working-set-assembler (F-06dfdad6): clad_get_working_set — one token-budgeted,
  code-bearing payload (focus + module code + forward needs + backward breaks +
  verify + budget). ~5-8x smaller than reading shard+files; clad_get_context frozen.
- graph-context-wiring (F-d6b93648): PostToolUse auto impact-card after source edits
  + ai_hints/persona nudge so the working set is actually used.

All three: strict gate GREEN + attested; 1603 tests pass; tsc/eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): context-efficiency tooling + the depends_on gap fix (F-96250595, F-2be3e3bb, F-15999130, F-16138071)

Four features that make cladding's knowledge graph populated + measurable, on
feature/ssot-knowledge-graph. The arc: 4 A/Bs found the graph tools NULL on agent
correctness — then re-framing to the real goal (search/context efficiency, not
"smarter agents") + fixing the empty-graph root cause turned it positive.

- iterative-impact-slice (F-96250595): buildIterativeImpactSlice — seed→widen→stop
  (coverage/exhaustion/marginal-yield), self-describing depth+stoppedBy+coverage;
  fixes the fixed-depth-1 narrow-miss. Calibrated on real graph data (2 sims).
- infer-depends-on (F-2be3e3bb): clad infer-deps reconstructs feature depends_on
  from the code import graph — the load-bearing graph edge cladding PRODUCED nowhere
  (vapt shipped 0 edges → empty graph → the real cause of the A/B NULLs). vapt 0→698.
- inferable-depends-on detector (F-15999130): INFERABLE_DEPENDS_ON — single info
  finding (never blocks, even strict) surfacing the empty-graph gap so infer-deps
  isn't a latent never-run tool. Detector count 38→39.
- graph-efficiency-measure (F-16138071): clad measure — deterministic per-feature
  search/context efficiency (no agent, no NULL risk). vapt: working-set 4.1x smaller
  context than naive (shard+all modules); dependency radius + regression set resolved
  for free. The goal axis, finally quantified positive.

Honest framing throughout (docs/ab-evaluation/*.md): the graph does NOT make a capable
agent more correct/cheaper (it greps regardless — NULL x4); its value is small context
+ explicit blast radius + known regression set + a queryable dependency graph, for
humans and tools that use it. infer-deps writes nothing (reviewable suggestions only).

All gated: 1624 tests GREEN, tsc/eslint clean, strict gate + attested per feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(graph): post-audit hygiene — clarify efficiency-report stats + drop dead StopReason

Read-only adversarial audit of the session found the work correct (4 features
conform, tests honest, claims reproduce, no regressions). Two low-severity hygiene
fixes, zero functional impact:

- docs/ab-evaluation/case-efficiency-measurement.md: the table wrote "3,028 tok vs
  14,442 tok = 4.1x (median)" on one line, which reads as a contradiction (14442/3028
  ≈ 4.8). Both numbers are correct but are DIFFERENT statistics — 4.1x is the median
  of per-feature naive÷slice ratios; 3,028/14,442 are independent medians of slice
  and naive sizes. Split them + added a note so the report can't be misread. (The
  audit verdict marked 4.1x "VERIFIED" and missed this; caught by hand arithmetic.)
- src/optimizer/iterative-slice.ts: removed 'token-budget' from the StopReason union
  — it was declared but never returned (budget overflow stops at max-depth first).
  Synced the test's ALLOWED_STOPS list.

1624 tests GREEN, tsc/eslint clean, strict gate re-attested (F-96250595).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(infer-deps): flag dynamic-import files for honest recall (F-0f2984d0)

The final read-only graph-traversal test on the real doverunner-vapt project
measured inference precision ~100% but recall ~70-75% — the largest remaining gap
being dynamic/runtime imports (importlib.import_module, __import__, getattr-based,
require(<expr>)) that static regex extraction cannot see (e.g. catalog/tool_inventory.py).

Rather than silently under-report those edges, inferDependsOn now collects such
module files into a sorted dynamicImportFiles list so a maintainer knows exactly
which files to review by hand. This is an honest-recall surface, NOT a precision
change — the static edges from the same files are still inferred unchanged (additive).
clad infer-deps prints dynamic_import_files.

Verified: cladding-self flags scripts/build.mjs (require). blind tests 3/3 in a
separate file (existing 6 untouched); 1627 tests GREEN; tsc/eslint clean; strict
gate re-attested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(graph): color system — hue=kind only, grouped spec/code/test/docs (F-5b188856)

The viewer's node colors had no clear basis: semanticHue let tier win over kind,
so one hue tried to encode two axes and collided — feature and scenario both
rendered blue, tier-C teal ≈ skill turquoise, and the sidebar showed the same
blue labeled both "Spec · sealed" (tier) and "feature" (kind). Tier is derivable
from kind, so encoding both was redundant.

Hue now encodes KIND only. semanticHue ignores tier; tier moves to the sidebar
filter + tooltip. KIND_COL becomes a grouped, simulation-verified palette
(all Y≥125 bloom floor, colorblind 4-group separation, hub-whitening distinctness):
  SPEC = blue family (feature/scenario/capability)
  module = orange (anchor) · test = green (anchor)
  DOCS = pink family (doc/skill) — skill is SKILL.md, a document, moved out of the
  old code-adjacent turquoise.

Sidebar: one color legend grouped into spec/code/test/docs zones; the SSoT tier
section is a swatch-less filter (checkbox + label only, no empty box). Tier labels
simplified to plain words (Spec / Design / Derived / Audit), and code nodes display
as "code" in the viewer (the spec's `modules:` data model is unchanged — display
label only).

Honest residuals (accepted, user-preferred anchors): module orange sits near the
0-45° health-burn arc, disambiguated by the burn's 2.2-3× pulse vs a static node;
orange↔green is the textbook red-green colorblind pair but is conceptually adjacent
(code↔test) with a luminance backstop.

1630 tests GREEN; tsc/eslint clean; strict gate re-attested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(readme-ko): graph section + live GIF, flat blue-token diagrams, content-accuracy pass

README.ko.html:
- enterprise trust/trace/scale hero; new project-map (graph) section with a live
  galaxy GIF, colour legend, and a clad-graph-serve launch guide
- refreshed numbers (39 detectors / 1630 tests / 196 features, 192 done / v0.7.0)
- unified plain-declarative voice; blue-primary emphasis (green = pass/success only)
- accurate 4-tier SSoT table (intent defined by humans, authored by the LLM per EARS)

docs/img/ko/*.svg (8): unified flat blue-token design system (no shadow/pastel/accent
bars, no version stamps); every claim verified against code — gate triggers 3/9/15 by
cost, authorship human-defines/LLM-writes, segregation-of-duties "aligns with" (not
"maps to") EU AI Act/SOX, Tier-B = project-context.md (not ai_hints), runner examples
= pure executors

docs/img/ko/graph.gif: 880px/8fps live-graph recording (6.9MB)
.gitignore: ignore *.mov (local screen recordings)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(attest): re-attest after develop merge — refresh verified tree-hashes

The merge moved 66 done features' module trees. A GREEN strict pre-push gate
(type/lint/unit/coverage/deliverable-smoke all pass; STALE_ATTESTATION was the
only outstanding finding) re-verified and re-stamped spec/attestation.yaml.
Drift is now clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(readme): sync all 4 READMEs + English diagrams to the finalized ko.html

Brings README.html, README.md, README.ko.md, and docs/img/en/*.svg to parity with
the finalized README.ko.html, and reconciles post-merge counts across every README.

- README.html / README.md — English mirror of ko.html: enterprise hero, the
  project-map (knowledge-graph) section with the live galaxy GIF + colour legend +
  clad-graph-serve launch guide, every section, locked numbers
- README.ko.md — Korean-markdown mirror (plain-declarative voice, docs/img/ko refs)
- docs/img/en/*.svg (8) — English of the finalized ko diagrams (identical geometry/
  style, terse English to fit fixed boxes) + docs/img/en/graph.gif
- counts reconciled everywhere: 40 detectors · 199 features (195 done) · 1664 tests ·
  169 test files · v0.7.0 · 2026-07 (was 196/192, 39, 1630, 2026-06 in places)
- ko.html: card "언제든 추적할 수 있다", Status 196/192 → 199/195, date → 2026-07
- content-accuracy carried to English: SoD "aligns with" (not "maps to") EU AI Act/SOX,
  Tier-B = project-context.md, runner examples = pure executors, gate 3/9/15 by cost,
  knowledge graph = traceability not correctness

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(attest): re-attest after all-READMEs lockstep

5 done features own README/diagram modules touched by the lockstep commit; a GREEN
strict pre-push gate (all other stages pass) re-verified and re-stamped
spec/attestation.yaml. Drift is clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(graph): flush stdout before exit — clad graph export truncated piped output at 64 KiB

runGraphExportCommand wrote the rendered graph to stdout then called process.exit(0)
on the next line; on a pipe, stdout.write is async, so exit killed the process before
the ~64 KiB OS pipe buffer drained — `clad graph export --format json | jq` truncated
at exactly 65536 B (this repo's payload is ~285 KiB). File / --out / serve / MCP
(synchronous or HTTP) were unaffected. Exit from the write callback instead; same
prophylactic fix for `graph stats`. Found by the pre-PR empirical verification sweep.

- src/cli/graph.ts: process.stdout.write(x, () => process.exit(0)) at both stdout sites
- tests/cli/graph-export-pipe.test.ts: spawns the CLI through a pipe, asserts the full
  >64 KiB JSON arrives and parses
- count reconciliation from the new test file: tests 1664→1665, test_files 169→170
  across the 4 READMEs + spec.yaml inventory; dist rebuilt; attestation re-stamped

Verified: `clad graph export --format json | jq` → 199 feature nodes (was truncated);
npm test 1665/1665 GREEN; pre-push strict gate green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(multi-agent): retitle diagram to "agent role separation" (was "persona permission separation")

The multi-agent diagram title used "페르소나 권한 분리" / "Persona Separation of Duties",
but the READMEs call these agents (not personas) and the concept is role/duty separation,
not permissions — and the diagram's own subtitle already says segregation-of-duties.
Aligned the title to that vocabulary:
- ko: "페르소나 권한 분리" → "에이전트 역할 분리"
- en: "Persona Separation of Duties" → "Agent Separation of Duties"
- ko/en multi-agent.svg (title + a11y) + all four README alt texts; attestation re-stamped

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(readme): make README.md + README.ko.md markdown-native (readability)

The two markdown READMEs mirrored the HTML too literally (layout <table>s, an
inline-styled Status block GitHub strips to bare text, centered <p>+<br> prose).
Rewrote them to native markdown — text verbatim, format only:
- layout 3-col tables → stacked sections / bullet trios (the hero hook,
  before/after/record, see/ask/measure)
- Status styled-table → one clean 5-col markdown table
- detector HTML table → markdown table; centered body prose → left-aligned + a blockquote
- one extra blank line before each main (##) section heading
- fixed a pre-existing KO detector-table count (spec↔test 5→6, so the rows sum to 40)
  and added the missing "capability 6개" to the KO Status footnote (EN parity)

HTML siblings (README.html, README.ko.html) and all diagrams left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(attest): re-stamp at 2026-07-01 — CI stale from last_synced crossing midnight

spec.yaml's inventory.last_synced is a module of F-5b9f9f / F-32b1e0 / F-d6b93648,
so when sync/check advances it to the current date, those features' attested tree-
hash goes stale. The branch was attested on 06-30; CI ran on 07-01 UTC and re-stamped
last_synced to 07-01, so the self-drift gate (`clad check --tier=pre-commit --strict`)
reported STALE_ATTESTATION. Re-attested against today so the committed spec.yaml +
attestation agree with a same-day CI run.

Follow-up (pre-existing, separate): last_synced should not churn attestation — exclude
it from the module hash, or stop writing it from check/build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(readme): trim prose for concision across all 4 READMEs

The before → verify → record loop was narrated ~5× at different altitudes. Cut the
cross-section redundancy and tightened wordy prose (~-93 lines; larger in rendered prose):
- "how it works with host LLM": dropped the intro + the entire After/Record subsections
  (re-told downstream in Gate / Detector / "done is earned") → one pointer line; kept Before
- removed the duplicate "no commands to memorize" line, the tagline-blurb flourish, the
  inline 9-stage list, and restated cost-split / colour-legend / Authority-column / ecosystem tails

The "기업이~ / To trust AI" tagline + its 3 trust cards are preserved byte-for-byte (core
hook, per maintainer). Numbers, honesty notes, diagrams, commands, and Docs links all intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Graph capability repairs — the 0.7.0 seams, simulation-verified (#225)

* fix(hygiene): replace raw NUL bytes with \u0000 escapes — sources were binary to git

src/graph/model.ts (edge-dedup key), src/optimizer/infer-depends-on.ts
(edge-map key), src/spec/attestation.ts (sha256 separators) carried literal
0x00 chars inside string/template literals. Git's binary heuristic then hid
those files from every diff/blame/review — the 0.7.0 graph core shipped as
"Bin 0 -> 9363 bytes" with a review-invisible diff. The escape spelling is
byte-identical at runtime (attestation digests unchanged; verified by
simulation before the change).

Adds a self-consistency test banning raw NUL bytes across the source tree
so a file can never silently become review-invisible again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(graph): legacy F-NNN ids restored to the doc axis via a shared F-id lexer

doc-references.ts matched only 6-8 hex ids, so every legacy sequential id
(F-001…F-083 — 80 live shards) referenced in docs/ produced no doc→feature
edge and no DOC_LINK_INTEGRITY validation, while graph-health.ts already
carried the correct alternation. src/spec/feature-id.ts is now the single
prose-scanning lexer for both sites (fresh RegExp per call — no shared /g
state). Simulated on the live repo before implementing: +10 refs gained,
all 10 resolve to real shards (0 new warns), hash-id extraction
byte-identical, references edges 8→18.

spec/_doc-links.yaml regeneration happens via clad sync at branch close.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(hook): PostToolUse impact card fires on host-style absolute paths

Hosts send tool_input.file_path ABSOLUTE while moduleOwners keys are the
spec's repo-relative posix paths, so buildImpactSlice never resolved and
the impact card (F-d6b93648) never rendered in real usage. Measured on
cladding-self before the fix: 0/361 module paths hit; after relativizing:
358/361 (99.2% — the 3 misses are trailing-slash directory keys unreachable
via the hook). Outside-repo absolute paths degrade to not_found, relative
inputs are untouched (idempotent). Card now also displays the relative path.

Adds the end-to-end wiring test (runHookEvent with an absolute file_path
against a real on-disk spec) that was missing when the bug shipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(working-set): module queries seed every co-owner; the budget now caps breaks_if_changed

Two seams in one module, landed together deliberately — simulation showed
the fan-out alone doubles budget-breach paths (17→33/144 on cladding-self,
worst 2.9x the cap), so the clip must ship with it.

Fan-out: a module-path query now passes its original form to the iterative
impact slice, seeding ALL owners (reverse-slice already supported this) —
before, only the alphabetically-first owner's dependents/tests were
reported (src/cli/clad.ts: impacted 0 vs 83). Co-owners are seeds, so they
surface in co_owners, not impacted; feature-id queries are byte-identical.

Clip: breaks_if_changed now participates in the token budget, LAST in the
clipping order (needs → code → breaks; the clip-before-code variant
simulated strictly worse). Deeper dependents drop from the far end first,
then tests outside the depth-1 floor; the direct set is never dropped
(must-edit precedent). Fit checks measure WITH the pending
'breaks: omitted …' marker, closing the +3..10-token overshoot the old
loops carried. In-budget payloads return byte-identical (pure no-op —
existing 6 tests unchanged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(measure): attribute cap-driven shrink honestly; plumb one reader through both sides

Two validity fixes to clad measure, both simulation-verified before coding:

Attribution: medianShrinkFactor was bounded by the 3000-token default budget
— on cladding-self the '~4x smaller' headline was mostly the CAP's
arithmetic, not graph value (uncapped, the structural slice is ~1.16x of
naive: code + structured metadata). The report now splits
fitsCount/truncatedCount with medianShrinkFit/medianShrinkTruncated and a
medianStructuralRatio, and the CLI headline attributes the reduction to the
budget ('budget enforces 3.9x on 163 capped') instead of selling it as
shrink. What the working set actually sells: a guaranteed token budget +
wired needs/breaks/verify context.

One universe: the injected ModuleReader now reaches buildWorkingSet →
codeExcerpt (same safety gates), so the slice and the naive baseline read
the SAME universe — before, a virtual reader fed only the baseline,
silently inflating the shrink factor ('Pure given (spec, reader)' is now
true). The old test asserted the inflated number; rewritten to assert the
honest split.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(serve): 503 error-as-data on unreadable spec; clean EADDRINUSE; watcher + Host hardening

Body-before-writeHead on every non-SSE route: a mid-write or unparseable
spec throws inside liveGraph(), and committing the 200+application/json
headers first turned that into an HTTP 200 whose body was a prose YAML
error — precisely the state the fs.watch auto-reload refetches into.
Parse failures now answer 503 with a JSON {error} payload (the viewer can
show a retry state); the headersSent guard remains for mid-stream (SSE)
failures. Also covers schema-invalid specs (simulation).

EADDRINUSE: server.on('error') now rejects the boot promise, so
runGraphServeCommand prints one pulse line and exits 1 — before, the
'error' event was unhandled (raw 20-line stack) and the promise never
settled. The listener stays attached so later runtime errors can't crash
the process either. FSWatcher 'error' events degrade to manual refresh
instead of crashing. A foreign Host header is refused (DNS-rebinding
guard; the bind was already 127.0.0.1).

Tests: the old 'watched-file change' test called broadcast() by hand —
renamed to what it tests, and a REAL fs.watch→debounce→SSE chain test
added (writes under spec/, capability-probe skip on platforms without
recursive watch), plus 503, busy-port, and Host-guard cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(graph): kind-twin union for focus queries; clad_get_graph summary; health cache + all-twin badges; live viewer fixes

Node identity: the same file materialises as up to three nodes
(module:/test:/doc: — 95 paths on cladding-self), and every focus surface
picked only the FIRST twin, silently dropping the others' edges.
resolveNodeIds returns all twins, subgraph accepts a seed set, and the CLI
--focus + clad_get_graph query union them (resolveNodeId keeps the old
single-id contract). graph-health now badges ALL twins of a finding's path
(first-twin-only left siblings looking healthy) and the viewer's drift
pill counts distinct paths so twins can't double the headline number.

clad_get_graph: the no-query form returned the whole graph pretty-printed
— measured 285KB (~70k tokens) in one MCP result, contradicting the
working-set budget discipline. It now answers a graphStats summary
(counts by kind + top hubs + a hint at the CLI export, 2.1KB); a focused
query still returns the real subgraph. The vacuous clad_get_working_set
test (asserted a hand-maintained constant against itself; the handler was
never invoked) is replaced with a real MCP round-trip: on-disk module code
in must_edit.code, dependent in breaks, budget echo, isError miss.

nodeHealth wraps its detector loop in primeSpecCache (the drift.ts
run-scope pattern): one shard-tree parse instead of ~10 per /health.json
request — measured 611ms → 21ms for the loop, results byte-identical
(incl. mtime-sensitive STALE_TESTS on a drifted fixture).

Viewer: SSE change detection compares the server's exact graph.json text
(baseline = first fetch; never re-serialized client-side) — the old
nodes.length proxy missed edge-only and same-count node changes and
rendered stale. Wires the dead mobile burger button. CLI: a typo'd
--format/--depth now fails loudly instead of silently rendering mermaid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(infer-deps): extract export…from re-exports + literal dynamic import(); TS fixtures added

The JS/TS branch missed two statically-extractable forms cladding's own
source uses: 'export … from' re-exports (barrel files are dependencies)
and literal dynamic import('…'). Both now produce edges. A NON-literal
import(expr) flags the file in dynamicImportFiles (kept apart from the
shared DYNAMIC_IMPORT regex — 'import (' would false-flag Python's
parenthesized from-import). The whole JS/TS extraction branch shipped with
zero fixtures (all Python) — a TS fixture set now pins import…from,
side-effect import, require(), re-exports, dynamic import, and the
single-segment ambiguity rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(render,serve,build): label escaping, corrupt-JSONL tolerance, three.js license notice

Mermaid ids are deduped (safeId collapsed src/a.ts vs src/a_ts into one
node silently); labels flatten quotes/newlines that broke the quoted-label
syntax. DOT escapes backslashes before quotes. Obsidian wikilink aliases
strip the |[[]] metacharacters that corrupt links. clad_get_events
tolerates a corrupt/partial JSONL line (mid-write tail read) as an
{unparseable} entry instead of crashing the tool call. The viewer bundle
keeps three.js's MIT notice (legalComments 'eof' — 'none' stripped it from
a distributed artifact).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: record the viewer decision reversal; correct 0.7.0 claims; cap-attribution note

design.md gains a dated post-ship addendum (§8): the 'no bespoke web UI'
decision was reversed (WebGL viewer + live serve shipped, and why the
premise no longer applied), clad_get_dependents shipped as clad_get_impact,
scope grew (working-set/iterative/infer-deps/measure), and _doc-links.yaml
is a grep/human index, not the export source. The 0.7.0 CHANGELOG described
the archived 2D prototype (force sliders, Live/Calm, 'no third-party
library') — corrected in place with a note, plus an [Unreleased] section
for this branch. Glossary's graph row now names html/serve. README no
longer claims serve 'opens' the browser. case-efficiency-measurement.md
carries the cap-attribution correction (the '4.1×' headline is
budget-enforced, not structural). Deprecation notices say 0.8 (0.7.0
shipped every alias while claiming removal in 0.7).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* spec(graph): shards track the corrected 0.7.1 contracts; sync + build artifacts

Updated ACs/test_refs where behavior contracts changed (clad_get_graph
no-query summary, working-set fan-out + breaks clipping, measure honest
attribution + one-reader universe, hook impact-card absolute-path wiring
now pinned end-to-end), each with a Correction note recording why.
src/spec/feature-id.ts claimed by the doc-graph feature. clad sync
regenerated spec/_doc-links.yaml — the restored legacy F-NNN doc edges
(multi-provider-roadmap, ssot-model) plus design.md's addendum references
now materialize. npm run build refreshed dist + plugin mirrors + the
viewer bundle (SSE text-compare + pill dedupe + burger + three.js MIT
notice at EOF). Persona alias deprecation wording aligned to 0.8.

Gate: clad check --tier=pre-push --strict GREEN (exit 0), full suite
1681/1681.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(graph): fallback safety contract — a graph answer that cannot know says so (F-c6a32fff) (#226)

The graph layer's miss/exception handling was solid, but three verified
holes let "unknown" read as "safe" — every fix design was simulated
against real repo data before implementation (one proposed fix was
invalidated by the simulation and dropped: promoting clad_get_impact to
the iterative slice would have silently shrunk 41% of responses).

Ledger honesty: every impact slice now carries spec-wide edge counts
{depends_on_edges, test_ref_edges}. On a blank ledger (measured on a
196-feature clone: a feature with 10 real dependents answered
impacted:[] coverage:1.0, byte-identical to a verified leaf) the answer
gains fallback hints — unknown, not safe; fall back to grep / the full
suite. Zero known dependents stops with 'no-known-dependents' +
coverage:null (44% of cladding-self features take this path); the
working-set radius carries the denominator and guards the JS null→0
coercion the simulation caught. The impact card discloses
'· deps unledgered'.

Hook scoping: Stop and PostToolUse now mirror SessionStart's spec.yaml
guard — a non-cladding cwd (or a monorepo subdir; hook cwd is
process.cwd()) used to get falsely BLOCKED by ABSENCE_OF_GOVERNANCE with
.cladding/ state written into the foreign tree (reproduced with the
shipped bundle). Not under cladding → silence, zero writes; a
present-but-broken spec keeps its honest one-time block.

Gate footer: an engine fault fabricated {pass:true} on the one
structural channel hook-less hosts see — now fails closed with
{pass:false, unavailable:true} (pass stays, per the F-570a3f wire
contract). The four graph MCP tools adopt the loadSpecOrError guidance
(was raw ENOENT), clad_get_graph misses gain the discovery hint, and
discovery hints name the baseline fallback. SessionStart renders an
unparseable spec with no resolvable counts as 'counts unavailable'
(conditional — a healthy spec/index.yaml still renders true counts).

Feature cycle: shard F-c6a32fff authored via clad_create_feature, all 6
ACs test_ref-wired, flipped done through the strict pre-push gate.
e2e-verified on the built binary: dense ledger {246,323} no hints /
blank ledger {0,0} both hints / spec-less Stop silent with no .cladding.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* Release v0.7.1 — Honest Graph (version bump + CHANGELOG + READMEs)

npm run version-bump -- 0.7.1 (9 sites incl. marketplace catalog) +
package-lock refresh. CHANGELOG [Unreleased] → [0.7.1] — 2026-07-02.
READMEs (md/ko.md/html/ko.html): status v0.7.1, tests 1691/1691,
features 200 (196 done).

Full suite 1691/1691 GREEN; clad check --tier=pre-push --strict GREEN.
Externally validated against npm 0.7.0 on a real 188-feature project:
23 scenarios, 15 measured improvements, 0 regressions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Sungju <yuyu04@naver.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant